//----------------------------------------------------------------------
// sgMotion Animation Library
//----------------------------------------------------------------------
//
// SunBurn open source animation library created and maintained
// by the SunBurn community.
//
// Based on the excellent (though abandoned) XNAnimation library, this
// project picks up development and provides full integration with both
// the SunBurn Engine and SunBurn Framework.
//
// Original XNAnimation library:
//
// http://www.codeplex.com/xnanimation
//
// A big thanks to Tom Looman for talking over this project, and Bruno
// Evangelista for his original work.
//
// Thanks guys!
//
//----------------------------------------------------------------------

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SgMotion;
using SgMotion.Controllers;
// Synapse Gaming namespaces
using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Editor;
using SynapseGaming.LightingSystem.Lights;
using SynapseGaming.LightingSystem.Rendering;
using System.IO;
#endregion

//----------------------------------------------------------------------
//---- Modified XNAnimation Library (based on: 0.7.0.0 BETA 2) ---------
//---- Original Creator: Bruno Evangelista -----------------------------
//---- Modified by: Tom Looman, Andy Dunn, cjHazard, and John Kabus ----
//---- Changes: SunBurn Integration (for version 2.0.18.7) -------------
//---- Updated sample & library code for SunBurn 2.0.18.7 (And XNA GS 4.0 Refresh / WP OS 7.1)
//---- Link to original library: http://www.codeplex.com/xnanimation ---
//---- Link to my Blog: http://coreenginedev.blogspot.com --------------
//----------------------------------------------------------------------

namespace SgMotionSample
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class AnimationSample : Game
    {
        private readonly GraphicsDeviceManager graphics;
        
        // The SunBurn lighting system.
        private readonly SunBurnCoreSystem sunBurnCoreSystem;
        private readonly SceneInterface sceneInterface;
        private readonly SceneState sceneState;
        private SceneEnvironment environment;
        private readonly SystemPreferences preferences;
        private readonly SplashScreenGameComponent splashScreenGameComponent;

        private  readonly FrameBuffers frameBuffers;

        // Text UI
        private SpriteBatch spriteBatch;
        private SpriteFont spriteFont;
        private SpriteFont spriteFontBig;

        // Input
        private KeyboardState keyboardState;
        private KeyboardState lastKeyboardState;

        private string message = "sgMotion";


        //Skinned stuff
        private int activeAnimationClip;
        private SkinnedModel skinnedModel;
        private AnimationController animationController;

        private Matrix cameraView;
        private Matrix cameraProjection;

        const string userPreferencesFile = "UserPreferences.xml";

        // SceneObject for our Marine model
        SceneObject marineObject;

        SpriteFont debugFont;

        public AnimationSample()
        {
            Content.RootDirectory = "Content";

            graphics = new GraphicsDeviceManager(this);

#if WINDOWS_PHONE
            graphics.PreferredBackBufferWidth = 480;
            graphics.PreferredBackBufferHeight = 800;
#else
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            graphics.PreferMultiSampling = true;
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;
#endif

            // Required for lighting system.
            splashScreenGameComponent = new SplashScreenGameComponent(this);
            Components.Add(splashScreenGameComponent);

            // Create the lighting system.
            sunBurnCoreSystem = new SunBurnCoreSystem(Services, Content);
            sceneState = new SceneState();

            // Create the scene interface. Acts as a service provider containing all scene managers
            // and returning them by type (including custom managers). Also acts as a component
            // container where calls to manager methods on the SceneInterface (such as BeginFrameRendering,
            // Unload, ...) are automatically called on all contained managers.
            //
            // This design allows managers to be plugged-in like modular components and for managers
            // to easily be added, removed, or replaced with custom implementations.
            //
            sceneInterface = new SceneInterface();
            sceneInterface.CreateDefaultManagers(RenderingSystemType.Forward, CollisionSystemType.Physics, true);

            frameBuffers = new FrameBuffers(DetailPreference.High, DetailPreference.High);

            // Load the user preferences (example - not required).
            preferences = new SystemPreferences();
#if WINDOWS
            if (File.Exists(userPreferencesFile))
                preferences.LoadFromFile(userPreferencesFile);
            else
#endif
            {
                preferences.EffectDetail = DetailPreference.High;
                preferences.MaxAnisotropy = 4;
                preferences.PostProcessingDetail = DetailPreference.High;
                preferences.ShadowDetail = DetailPreference.High;
                preferences.ShadowQuality = 1.0f;

                preferences.TextureSampling = SamplingPreference.Anisotropic;
            }
        }

        /// <summary>
        /// Improves overall performance with dynamic shadows.
        /// </summary>
        private void PrepareDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            e.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PlatformContents;
        }


        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Fonts/Arial");
            spriteFontBig = Content.Load<SpriteFont>("Fonts/Calibri");

            // Load the skinned model
            skinnedModel = Content.Load<SkinnedModel>("Models/PlayerMarine");

            // Create an animation controller and start a clip
            animationController = new AnimationController(skinnedModel.SkeletonBones);
            animationController.StartClip(skinnedModel.AnimationClips[skinnedModel.AnimationClips.Keys[0]]);

            // Setup camera
            cameraView = Matrix.CreateLookAt(new Vector3(0, 15, 40), new Vector3(0, 10, 0), Vector3.Up);
            cameraProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 
                graphics.GraphicsDevice.Viewport.Width / (float)graphics.GraphicsDevice.Viewport.Height, 1, 1000);

            Scene scene = Content.Load<Scene>("Scene/Scene");

#if WINDOWS_PHONE
            // Set the lights as "static", as these are supported by WP7.
            foreach (ILightGroup group in scene.LightGroups)
            {
                foreach (ILight light in group.Lights)
                    light.LightingType = LightingType.BakedDown;
            }
#endif

            sceneInterface.ObjectManager.Submit(scene);
            sceneInterface.LightManager.Submit(scene);

            // Load the scene settings.
            environment = Content.Load<SceneEnvironment>("scene_environment");

            // Apply the user preferences (example - not required).
            sceneInterface.ApplyPreferences(preferences);

            // SceneObject setup
            marineObject = new SceneObject(skinnedModel.Model)
                               {
                                   UpdateType = UpdateType.None,
                                   Visibility = ObjectVisibility.RenderedAndCastShadows,
                                   StaticLightingType = StaticLightingType.Composite,
                                   World = Matrix.Identity
                               };

            // Submit the SceneObject so it can be rendered
            sceneInterface.ObjectManager.Submit(marineObject);

            debugFont = Content.Load<SpriteFont>("Fonts/debugbonefont");

            animationController.TranslationInterpolation = InterpolationMode.Linear;
            animationController.OrientationInterpolation = InterpolationMode.Linear;

        }

        #region Unload
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here

            sceneInterface.Unload();
            sunBurnCoreSystem.Unload();
        }
        #endregion

        protected override void Update(GameTime gameTime)
        {
            // Allows game controls when not editing the scene.
            if (!sceneInterface.Editor.EditorAttached)
            {
                #region Input
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                    Keyboard.GetState().IsKeyDown(Keys.Escape))
                    this.Exit();

                lastKeyboardState = keyboardState;
                keyboardState = Keyboard.GetState();

                if (keyboardState.IsKeyDown(Keys.Escape))
                    Exit();

                if (keyboardState.IsKeyDown(Keys.Left) &&
                    lastKeyboardState.IsKeyUp(Keys.Left))
                {
                    activeAnimationClip = (activeAnimationClip - 1);
                    if (activeAnimationClip < 0)
                        activeAnimationClip = skinnedModel.AnimationClips.Count - 1;
                    animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClip], TimeSpan.FromSeconds(0.3f));

                }
                else if (keyboardState.IsKeyDown(Keys.Right) &&
                    lastKeyboardState.IsKeyUp(Keys.Right))
                {
                    activeAnimationClip = (activeAnimationClip + 1) % skinnedModel.AnimationClips.Count;
                    animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClip], TimeSpan.FromSeconds(0.3f));
                }

                if (keyboardState.IsKeyDown(Keys.PageUp))
                {
                    message = "Custom Per-Bone Transformation";
                }
                else if (keyboardState.IsKeyDown(Keys.PageDown))
                {
                    message = "Custom Per-Bone Transformation";
                }

                if (keyboardState.IsKeyDown(Keys.F1))
                {
                    message = "No Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.None;
                    animationController.OrientationInterpolation = InterpolationMode.None;
                    animationController.ScaleInterpolation = InterpolationMode.None;
                }
                else if (keyboardState.IsKeyDown(Keys.F2))
                {
                    message = "Linear Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.Linear;
                    animationController.OrientationInterpolation = InterpolationMode.Linear;
                    animationController.ScaleInterpolation = InterpolationMode.Linear;
                }
                else if (keyboardState.IsKeyDown(Keys.F3))
                {
                    message = "Cubic Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.Cubic;
                    animationController.OrientationInterpolation = InterpolationMode.Linear;
                    animationController.ScaleInterpolation = InterpolationMode.Cubic;
                }
                else if (keyboardState.IsKeyDown(Keys.F4))
                {
                    message = "Spherical Interpolation";
                    animationController.TranslationInterpolation = InterpolationMode.Linear;
                    animationController.OrientationInterpolation = InterpolationMode.Spherical;
                    animationController.ScaleInterpolation = InterpolationMode.Linear;
                }


                if (keyboardState.IsKeyDown(Keys.D5) && lastKeyboardState.IsKeyUp(Keys.D5))
                {
                    message = "Animation Looping Control";
                    animationController.LoopEnabled = !animationController.LoopEnabled;
                }

                if (keyboardState.IsKeyDown(Keys.Tab) && lastKeyboardState.IsKeyUp(Keys.Tab))
                {
                    message = "Forward and Backward Playback";

                    if (animationController.PlaybackMode == PlaybackMode.Forward)
                        animationController.PlaybackMode = PlaybackMode.Backward;
                    else
                        animationController.PlaybackMode = PlaybackMode.Forward;
                }

                if (keyboardState.IsKeyDown(Keys.Up))
                {
                    message = "Animation Speed Control";
                    animationController.Speed += 0.005f;
                }
                else if (keyboardState.IsKeyDown(Keys.Down))
                {
                    message = "Animation Speed Control";
                    animationController.Speed -= 0.005f;
                    if (animationController.Speed < 0.1f)
                        animationController.Speed = 0.1f;
                }

                // cycle bones
                if (keyboardState.IsKeyDown(Keys.OemPeriod) && lastKeyboardState.IsKeyUp(Keys.OemPeriod))
                {
                    count++;
                    if (count > animationController.SkinnedBoneTransforms.Length - 1)
                        count = 0;
                    animationController.SetSelectedBone(count); 
                }
                else if (keyboardState.IsKeyDown(Keys.OemComma) && lastKeyboardState.IsKeyUp(Keys.OemComma))
                {
                    count--;
                    if (count < 0)
                        count = animationController.SkinnedBoneTransforms.Length - 1;
                    animationController.SetSelectedBone(count);
                }

                #endregion
            }

            // Rotate the marine
            marineObject.World *= Matrix.CreateFromAxisAngle(Vector3.Up, 0.01f);

            // Update the bone transforms
            marineObject.SkinBones = animationController.SkinnedBoneTransforms;

            // Keep the sceneInterface updated
            sceneInterface.Update(gameTime);

            // Update the animation controller
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            //// Select a bone to be displayed in a different color with highlighted child-bones.
            //delta += (float)gameTime.ElapsedGameTime.TotalSeconds;
            //if (delta >= 2f)
            //{
            //    animationController.SetSelectedBone(count);

            //    count++;
            //    if (count > animationController.SkinnedBoneTransforms.Length - 1)
            //        count = 0;
            //    delta = 0;
            //}
 
            base.Update(gameTime);
        }

        int count = 0;
        //float delta = 0;

        protected override void Draw(GameTime gameTime)
        {
            // Display Splashscreen on start
            if (!SplashScreenGameComponent.DisplayComplete)
            {
                base.Draw(gameTime);
                return;
            }

            // Render the scene.
            sceneState.BeginFrameRendering(cameraView, cameraProjection, gameTime, environment, frameBuffers, true);
            sceneInterface.BeginFrameRendering(sceneState);

            // Add custom rendering that should occur before the scene is rendered.

            sceneInterface.RenderManager.Render();

            // Add custom rendering that should occur after the scene is rendered.
            graphics.GraphicsDevice.DepthStencilState = DepthStencilState.None;

            animationController.DrawDebugSkeleton(marineObject.World, cameraView, cameraProjection, GraphicsDevice, debugFont, DebugDisplay.Always, DebugDisplay.Always);

            graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            Draw2D();

            sceneInterface.EndFrameRendering();
            sceneState.EndFrameRendering();

            base.Draw(gameTime);
        }

        #region Draw2D
        private void Draw2D()
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(spriteFont,
                "Model: " + "Marine" + "    Animation: " +
                    skinnedModel.AnimationClips.Keys[activeAnimationClip], new Vector2(10, 10),
                Color.White);

            spriteBatch.DrawString(spriteFont, "Speed: ", new Vector2(10, 40), Color.White);
            spriteBatch.DrawString(spriteFont, "Looping: ", new Vector2(10, 60), Color.White);
            spriteBatch.DrawString(spriteFont, "Blending: ", new Vector2(10, 90), Color.White);
            spriteBatch.DrawString(spriteFont, "Playing: ", new Vector2(10, 130), Color.White);

            spriteBatch.DrawString(spriteFont, "" + (int) (animationController.Speed * 1000) * 0.001f,
                new Vector2(140, 40), Color.Red);
            spriteBatch.DrawString(spriteFont, "" + animationController.LoopEnabled, new Vector2(140, 60),
                Color.Red);
            spriteBatch.DrawString(spriteFont, "None", new Vector2(140, 90), Color.Red);
            spriteBatch.DrawString(spriteFont, "" + animationController.PlaybackMode,
                new Vector2(140, 130), Color.Red);

            spriteBatch.DrawString(spriteFont, message, new Vector2(10, GraphicsDevice.Viewport.Height - spriteFont.MeasureString(message).Y), Color.SkyBlue);
            spriteBatch.End();
        }
        #endregion
    }
}